package peerToPeerFacebook.proj;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/** This class controls access to resources. It make sure that only one process can have access to the write version of
 * resource. Multipl reades are fine.
 */
public class LockManager
{
	/** Contains list of read locks that are obtained and owners of them */
	public Map<String, Set<String>> readLocks;
	
	/** Contains list of write locks that are obtained and owners of them */
	public Map<String, Set<String>> writeLocks;

	/** Constructs lock manager. */
	public LockManager()
	{
		this.readLocks = new HashMap<String, Set<String>>();
		this.writeLocks = new HashMap<String, Set<String>>();
	}
	/** Tries to obtain read lock for given owner with given name. This lock can be obtained only if no one holds write
	 * lock with the same name. Multiple ones holding read lock is fine.
	 * 
	 * @param owner Owner of the transaction that should own this lock.
	 * @param name Name of the lock.
	 * @return True of the lock can be obtained.
	 */
	public boolean tryReadLock(String owner, String name)
	{
		// quick check if we already have lock
		if (readLocks.get(name) != null && readLocks.get(name).contains(owner))
			return true;
		
		// If someone has a write lock - then no go
		if(writeLocks.get(name) != null)
		{
			Set<String> wLocks = writeLocks.get(name);
			if (!(wLocks.contains(owner) && wLocks.size() == 1) && !(!wLocks.contains(owner) && wLocks.size() == 0))
				return false;
		}
		
		// Hey we can give out read lock. Just do house keeping
		if (readLocks.get(name) == null)
			readLocks.put(name, new HashSet<String>());
			
		readLocks.get(name).add(owner);
		return true;
	}

	/** Tries to obtain write lock. Write lock can be obtained if the only lock that is held on this resource is
	 * held by the same owner.
	 * 
	 * @param owner Owner that should be associated with this lock
	 * @param name Name of the lock
	 * @return True if the lock has been obtained.
	 */
	public boolean tryWriteLock(String owner, String name)
	{
		// quick check if we already have lock
		if (writeLocks.get(name) != null && writeLocks.get(name).contains(owner))
			return true;

		// Ok, long way. Check who has read lock
		if(readLocks.get(name) != null)
		{
			Set<String> rLocks = readLocks.get(name);
			if (!(rLocks.contains(owner) && rLocks.size() == 1) && !(!rLocks.contains(owner) && rLocks.size() == 0))
				return false;
		}
		
		// create record for future
		Set<String> wLocks = writeLocks.get(name);
		if (wLocks == null)
		{
			wLocks = new HashSet<String>();
			writeLocks.put(name, wLocks);
		}
		
		if (!(wLocks.contains(owner) && wLocks.size() == 1) && !(!wLocks.contains(owner) && wLocks.size() == 0))
			// someone else has a lock
			return false;

		// Now we can get a lock
		wLocks.add(owner);
		return true;
	}
	
	/** Releases lock owned by owner.
	 * 
	 * @param owner Owner that owns the lock
	 * @param name Lock name
	 */
	public void releaseLock(String owner, String name)
	{
		// Drop from read and write locks
		if (readLocks.get(name) != null)
			readLocks.get(name).remove(owner);
		
		if (writeLocks.get(name) != null)
			writeLocks.get(name).remove(owner);
	}
	
	/** Resturns a set of read owners associated with this lock.
	 * 
	 * @param name Lock name
	 * @return Set of owners. Returned set should not be modified.
	 */
	public Set<String> readOwners(String name)
	{
		if (!readLocks.containsKey(name))
			readLocks.put(name, new HashSet<String>());
		
		return readLocks.get(name);
	}
	/** Resturns a set of read owners associated with this lock.
	 * 
	 * @param name Lock name
	 * @return Set of owners. Returned set should not be modified
	 */
	public Set<String> writeOwners(String name)
	{
		if (!writeLocks.containsKey(name))
			writeLocks.put(name, new HashSet<String>());
		
		return writeLocks.get(name);
	}
}